﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using MentalAlchemy.Atomics;
using MentalAlchemy.Molecules.MachineLearning;

namespace MentalAlchemy.Molecules.MachineLearning
{
	/// <summary>
	/// [molecule]
	/// 
	/// Implementation of GHA with dynamic removal of linear subspaces.
	/// See (the idea):
	/// Tsoy Y. Evolving Linear Neural Networks for Features Space Dimensionality Reduction.
	///	Proceedings of the 2012 IEEE International Joint Conference on Neural Networks (IJCNN 2012).
	///	Brisbane, Australia, June, 10-15, 2012.
	/// </summary>
	public class DGHA : GHA
	{
		public float Tau = 10;	// Parameter for removal of subspaces.

		/// <summary>
		/// [molecule]
		/// 
		/// Performs training of ANN using given data for a specified number of epochs.
		/// </summary>
		/// <param name="net"></param>
		/// <param name="data"></param>
		/// <param name="steps"></param>
		/// <param name="results"></param>
		public virtual LayeredNeuralNetwork Train(LayeredNeuralNetwork net, List<TrainingSample> data, int steps, out DGhaResults results)
		{
			results = new DGhaResults();

			var epochStep = 5;
			var timer = new Stopwatch();
			timer.Start();
			results.Weights.Add(net.GetConnectionWeights().ToArray());	// register initial weights
			results.OutputsCount.Add(net.OutputsNumber);			// and initial number of outputs.
			for (int i = 0; i < steps; i++)
			{
				//timer.Stop();
				#region - Log weights. -
				if (i % epochStep == 0 && i != 0)
				{	// remember weights after each [epochStep] generations.
					results.Weights.Add(net.GetConnectionWeights().ToArray());
				}
				#endregion

				#region - Compute output variances. -
				var outs = MachineLearningElements.GetOutputs(net, data);
				var vars = VectorMath.VarianceVector(outs);

				// if vars are not sorted than sort outputs.
				if (!VectorMath.IsSorted(vars))
				{
					// sort [vars] and nodes in the descending order.
					MathNet.Numerics.Sorting.Sort(vars, net.Layers[1].Nodes);
					vars = VectorMath.Reverse(vars);
					net.Layers[1].Nodes = VectorMath.Reverse(net.Layers[1].Nodes);
				}

				results.OutputVars.Add(vars);
				#endregion

				#region - Decide whether outputs should be removed. -
				for (int j = vars.Length-1; j > 0; j--)
				{
					var rate = vars[0]/vars[j];
					if (rate > Tau)
					{	// remove j-th output.
						net.Layers[1].Nodes.RemoveAt(j);
					}
					else
					{	// all outputs are assumed to be sorted by projections variance.
						break;
					}
				}
				results.OutputsCount.Add(net.OutputsNumber);
				#endregion
				//timer.Start();

				TrainingRate = 1f / (i + 1);
				UpdateWeights(ref net, data, DataAmount);
			}
			timer.Stop();

			results.ElapsedTime = timer.ElapsedMilliseconds;
			results.Weights.Add(net.GetConnectionWeights().ToArray());	// remember resulting weights.
			results.OutputsCount.Add(net.OutputsNumber);
			results.EpochStep = epochStep;
			return net;
		}
	}

	public class DGhaResults : GhaResults
	{
		public List<int> OutputsCount = new List<int>();	// number of outputs.

		/// <summary>
		/// [molecule]
		/// 
		/// Returns lines for log-file using data from the given GHA results.
		/// </summary>
		/// <returns></returns>
		public override List<string> ConvertToLogLines()
		{
			var res = new List<string>();

			// write elapsed time.
			res.Add("> Elapsed time, msec:\t" + ElapsedTime);

			// write variances.
			res.Add("\n> Outputs variances:");
			var count = 0;
			foreach (var outputVar in OutputVars)
			{
				res.Add(count + "\t" + VectorMath.ConvertToString(outputVar, '\t'));
				count++;
			}

			// write weights.
			res.Add("\n> ANN weights:");
			int epochCount = 0;
			foreach (var ws in Weights)
			{
				res.Add("\nEpoch #" + epochCount);
				var w = VectorMath.Split(ws, OutputsCount[epochCount]);
				var m = MatrixMath.CreateFromColsList(w);
				res.AddRange(MatrixMath.ConvertToRowsStringsList(m, '\t'));
				epochCount += EpochStep;
			}

			// write dimensionality.
			res.Add("\n> Output dimensionality change:");
			res.AddRange( VectorMath.ConvertToStringsArray(OutputsCount.ToArray()) );

			return res;
		}
	}

	public class MeanDGhaResults : MeanGhaResults
	{
		public List<Stats> OutputStats;	// Stats for the number of outputs.

		/// <summary>
		/// [molecule]
		/// 
		/// Compute mean GHA results from the given list of individual run results.
		/// Note: Each run should be performed with the same settings.
		/// </summary>
		/// <param name="res"></param>
		/// <returns></returns>
		public static MeanDGhaResults Compute(List<DGhaResults> res)
		{
			//var mean = MeanGhaResults.Compute(res);

			//var mean = new MeanGhaResults();
			var dghaRes = new MeanDGhaResults();

			var times = new List<int>();
			foreach (var ghaRes in res)
			{
				MatrixMath.AccumulateSafe(ref dghaRes.OutputVars, ghaRes.OutputVars);
				//MatrixMath.AccumulateSigned(ref dghaRes.Weights, ghaRes.Weights);
				times.Add((int)ghaRes.ElapsedTime);
			}

			var tempM = MatrixMath.CreateFromRowsListSafe(dghaRes.OutputVars);
			tempM = MatrixMath.Mul(tempM, 1f / res.Count);
			dghaRes.OutputVars = MatrixMath.ConvertToRowsList(tempM);	// compute mean vars.
			//tempM = MatrixMath.CreateFromRowsList(dghaRes.Weights);
			//tempM = MatrixMath.Mul(tempM, 1f / res.Count);
			//dghaRes.Weights = MatrixMath.ConvertToRowsList(tempM);	// compute mean weights.

			dghaRes.TimeStats = VectorMath.CalculateStats(times.ToArray());
			dghaRes.EpochStep = res[0].EpochStep;
			dghaRes.OutputStats = new List<Stats>();

			var outCounts = new List<int[]>();
			foreach (var ghaResults in res)
			{
				var temp = ghaResults;
				outCounts.Add(temp.OutputsCount.ToArray());
			}

			var tr = MatrixMath.Transpose(outCounts);	// tr contains outputs count per generations.
			foreach (var row in tr)
			{
				dghaRes.OutputStats.Add(VectorMath.CalculateStats(row));
			}

			return dghaRes;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns lines for log-file using data from the given DGHA results.
		/// </summary>
		/// <returns></returns>
		public override List<string> ConvertToLogLines()
		{
			var res = base.ConvertToLogLines();

			// write outputs number stats.
			res.Add("\n> Outputs count stats:");
			foreach (var stats in OutputStats)
			{
				res.Add(stats.GetStatsString());
			}

			return res;
		}
	}
}
